/**
 * Copyright (C) 2008 by The Regents of the University of California
 * Redistribution of this file is permitted under the terms of the GNU
 * Public License (GPL).
 *
 * @author Junghoo "John" Cho <cho AT cs.ucla.edu>
 * @date 3/24/2008
 */

#include <cstdio>
#include <iostream>
#include <fstream>
#include "Bruinbase.h"
#include "SqlEngine.h"
#include "BTreeIndex.h"

using namespace std;

// external functions and variables for load file and sql command parsing 
extern FILE* sqlin;
int sqlparse(void);


RC SqlEngine::run(FILE* commandline)
{
  fprintf(stdout, "Bruinbase> ");

  // set the command line input and start parsing user input
  sqlin = commandline;
  sqlparse();  // sqlparse() is defined in SqlParser.tab.c generated from
               // SqlParser.y by bison (bison is GNU equivalent of yacc)

  return 0;
}

RC SqlEngine::select(int attr, const string& table, const vector<SelCond>& cond)
{
  RecordFile rf;   // RecordFile containing the table
  RecordId   rid;  // record cursor for table scanning

  RC     rc;
  int    key;     
  string value;
  int    count;
  int    diff;
  
  BTreeIndex bti;
  IndexCursor ic;
  
  bti.setTreeHeight(getTreeHeight());
  
  bti.setRootPid(getRootPid());
  
  bool keyCond = false;
  
  int kMin = 0;
  int kMax = 1000000000;
  
  int kEQ = -1;
    
  vector<int> kNE;
  
  char* vMin = "";
  char* vMax = "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ";
  
  char* vEQ = NULL;
    
  vector<char*> vNE;
  
  
  
  // open the table file
  if ((rc = rf.open(table + ".tbl", 'r')) < 0) {
    fprintf(stderr, "Error: table %s does not exist\n", table.c_str());
    return rc;
  }  
  
  if ((rc = bti.open(table, 'r')) < 0) {
          goto without_index;
        }
  


  
 
  for (unsigned i = 0; i < cond.size(); i++) {
    if (cond[i].attr == 1) // condition on key
    {
      keyCond = true;
      if (atoi(cond[i].value) < 0)
      {
        fprintf(stderr, "Error: invalid key value %d specified\n", atoi(cond[i].value));
      }
      keyCond = true;
      if (cond[i].comp == SelCond::EQ)
      { kEQ = atoi(cond[i].value); }
      
      else if (cond[i].comp == SelCond::NE)
      { kNE.push_back(atoi(cond[i].value)); }
      
      else if (cond[i].comp == SelCond::LT)
      {
        if (kMax > atoi(cond[i].value))
        { kMax = atoi(cond[i].value); }
      }
      
      else if (cond[i].comp == SelCond::GT)
      {
        if (kMin < atoi(cond[i].value))
        { kMin = atoi(cond[i].value); }
      }
         
      else if (cond[i].comp == SelCond::LE)
      {
        if (kMax > atoi(cond[i].value)+1)
        { kMax = atoi(cond[i].value); }
      }
      
      else if (cond[i].comp == SelCond::GE)
      {
        if (kMin < atoi(cond[i].value)-1)
        { kMin = atoi(cond[i].value); }
      }
    }
    
    
    if (cond[i].attr == 2) // condition on value
    {
      if (cond[i].comp == SelCond::EQ)
      { vEQ = cond[i].value; }
      
      else if (cond[i].comp == SelCond::NE)
      { vNE.push_back(cond[i].value); }
      
      else if (cond[i].comp == SelCond::LT)
      {
        if (vMax == NULL)
        { vMax = cond[i].value; }
        else if (vMax > cond[i].value)
        { vMax = cond[i].value; }
      }
      
      else if (cond[i].comp == SelCond::GT)
      {
        if (vMin == NULL)
        { vMin = cond[i].value; }
        else if (vMin < cond[i].value)
        { vMin = cond[i].value; }
      }
         
      else if (cond[i].comp == SelCond::LE)
      {
        if (vMax == NULL)
        { vMax = cond[i].value; }
        else if (vMax > cond[i].value+1)
        { vMax = cond[i].value; }
      }
      
      else if (cond[i].comp == SelCond::GE)
      {
        if (vMin == NULL)
        { vMin = cond[i].value; }
        else if (vMin < cond[i].value-1)
        { vMin = cond[i].value; }
      }
    }
  }
  
  if (keyCond == false)
    { goto without_index; }
  if (kEQ > -1 && kMin < kEQ && kMax > kEQ) // If there exists a valid equality condition on key
  {   
    bti.locate(kEQ, ic);
    bti.readForward(ic, key, rid);
    
    rf.read(rid, key, value);
    if (vEQ != NULL)
    {
      if (value == vEQ && vMin < vEQ && vMax > vEQ)
      {
        count++;
        // print the tuple 
        switch (attr) {
          case 1:  // SELECT key
            fprintf(stdout, "%d\n", key);
            break;
          case 2:  // SELECT value
            fprintf(stdout, "%s\n", value.c_str());
            break;
          case 3:  // SELECT *
            fprintf(stdout, "%d '%s'\n", key, value.c_str());
            break;            
        }        
      }
    }
  }
  else if (kMin < kMax && kEQ == -1)
  {   
    key = kMin;
    bti.locate(key, ic);
    next_while:    
    while (bti.readForward(ic, key, rid) == 0)
    {
      if (key > kMin && key < kMax)
      {
        for (unsigned i = 0; i < kNE.size(); i++) {
          if (kMin == kNE[i])
          { goto next_while; }  
        }
        rf.read(rid, key, value);
        if (vEQ != NULL) // If EQ condition exists on value
        {
          if (vMin < vEQ && vEQ < vMax) // If EQ condition is valid
          {
            if (vEQ == value)
            {
              count++;
              // print the tuple 
              switch (attr) {
                case 1:  // SELECT key
                  fprintf(stdout, "%d\n", key);
                  break;
                case 2:  // SELECT value
                  fprintf(stdout, "%s\n", value.c_str());
                  break;
                case 3:  // SELECT *
                  fprintf(stdout, "%d '%s'\n", key, value.c_str());
                  break;            
              }
            }        
          }
          else // If EQ condition is NOT valid
          {
            rc = 0;
            goto exit_select;
          }   
        }
        else // If no EQ condition exists
        {
          if (vMin < vMax)
          {
            if (vMin < value && value < vMax)
            {
              for (unsigned i = 0; i < vNE.size(); i++) {
                if (value == vNE[i])
                { goto next_while; }  
              }
              count++;
              // print the tuple 
              switch (attr) {
                case 1:  // SELECT key
                  fprintf(stdout, "%d\n", key);
                  break;
                case 2:  // SELECT value
                  fprintf(stdout, "%s\n", value.c_str());
                  break;
                case 3:  // SELECT *
                  fprintf(stdout, "%d '%s'\n", key, value.c_str());
                  break;            
              }
            }
            else // If EQ condition is NOT valid
            {
              rc = 0;
              goto exit_select;
            }
          }
        }
      }
    }
  }
  if (attr == 4) {
    fprintf(stdout, "%d\n", count);
  }
  rc = 0;
  
  goto exit_select;



  without_index:
    
  // scan the table file from the beginning
  rid.pid = rid.sid = 0;
  count = 0;
    

  
  while (rid < rf.endRid()) {
    // read the tuple
    if ((rc = rf.read(rid, key, value)) < 0) {
      fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
      goto exit_select;
    }

    // check the conditions on the tuple
    for (unsigned i = 0; i < cond.size(); i++) {
      // compute the difference between the tuple value and the condition value
      switch (cond[i].attr) {
      case 1:
	diff = key - atoi(cond[i].value);
	break;
      case 2:
	diff = strcmp(value.c_str(), cond[i].value);
	break;
      }

      // skip the tuple if any condition is not met
      switch (cond[i].comp) {
      case SelCond::EQ:
	if (diff != 0) goto next_tuple;
	break;
      case SelCond::NE:
	if (diff == 0) goto next_tuple;
	break;
      case SelCond::GT:
	if (diff <= 0) goto next_tuple;
	break;
      case SelCond::LT:
	if (diff >= 0) goto next_tuple;
	break;
      case SelCond::GE:
	if (diff < 0) goto next_tuple;
	break;
      case SelCond::LE:
	if (diff > 0) goto next_tuple;
	break;
      }
    }

    // the condition is met for the tuple. 
    // increase matching tuple counter
    count++;

    // print the tuple 
    switch (attr) {
    case 1:  // SELECT key
      fprintf(stdout, "%d\n", key);
      break;
    case 2:  // SELECT value
      fprintf(stdout, "%s\n", value.c_str());
      break;
    case 3:  // SELECT *
      fprintf(stdout, "%d '%s'\n", key, value.c_str());
      break;
    }

    // move to the next tuple
    next_tuple:
    ++rid;
  }

  // print matching tuple count if "select count(*)"
  if (attr == 4) {
    fprintf(stdout, "%d\n", count);
  }
  rc = 0;

  // close the table file and return
  exit_select:
  rf.close();
  return rc;
}

RC SqlEngine::load(const string& table, const string& loadfile, bool index)
{
	/* Initialize RecordFile and RecordId objects */
	RecordFile rf;
	RecordId rid;

	RC rc;

  /* Open table file */
	if ((rc = rf.open(table + ".tbl", 'w')) < 0)
	{
		fprintf(stderr, "Error in opening table %s\n", table.c_str());
		return rc;
	}

  /* Local variables to hold values that will be inserted */
	int key;
	string value;

  /* Open loadfile */
  fstream fstr;
  fstr.open(loadfile.c_str(), fstream::in | fstream::out);

  /* Case 1: no index */
  if(index == false) {
    /* Store into table */
    while(!fstr.eof())
    {
      char store[1000];
      fstr.getline(store,1000);
      if(fstr.eof()) { goto exit_select; }
      parseLoadLine(store, key, value);
      rf.append(key, value, rid);
    }
  }

  /* Case 2: index!! */
  else {
    /* Create index file */
    BTreeIndex bti;
    bti.open(table, 'w');

    /* Fill table and index */
    while(!fstr.eof())
    {
      char store[10000];
      fstr.getline(store,10000);
      if(fstr.eof()) { goto exit_bti; }
                                           
      /* Store key-value pairs into table */
      parseLoadLine(store, key, value);
      rf.append(key, value, rid);

      /* Store key-rid pairs into index */
      bti.insert(key, rid);
    }
    exit_bti:
    int treeHeight = bti.getTreeHeight();
    setTreeHeight(treeHeight);
    
    int rootPid = bti.getRootPid();
    setRootPid(rootPid);
    bti.close();
  }

  exit_select:
  fstr.close();
  rf.close();
  return 0;
}

RC SqlEngine::parseLoadLine(const string& line, int& key, string& value)
{
    const char *s;
    char        c;
    string::size_type loc;
    
    // ignore beginning white spaces
    c = *(s = line.c_str());
    while (c == ' ' || c == '\t') { c = *++s; }

    // get the integer key value
    key = atoi(s);

    // look for comma
    s = strchr(s, ',');
    if (s == NULL) { return RC_INVALID_FILE_FORMAT; }

    // ignore white spaces
    do { c = *++s; } while (c == ' ' || c == '\t');
    
    // if there is nothing left, set the value to empty string
    if (c == 0) { 
        value.erase();
        return 0;
    }

    // is the value field delimited by ' or "?
    if (c == '\'' || c == '"') {
        s++;
    } else {
        c = '\n';
    }

    // get the value string
    value.assign(s);
    loc = value.find(c, 0);
    if (loc != string::npos) { value.erase(loc); }

    return 0;
}

RC SqlEngine::setTreeHeight(int treeHeight)
{
  g_treeHeight = treeHeight;
  return 0;  
}

int SqlEngine::getTreeHeight()
{
  return g_treeHeight;  
}

RC SqlEngine::setRootPid(int rootPid)
{
  g_rootPid = rootPid;
  return 0;  
}

int SqlEngine::getRootPid()
{
  return g_rootPid;  
}
